use anyhow::Result;
use colored::Colorize;
use serde_json::Value;
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;

use crate::jwt;
use crate::utils;

/// Options for encoding operations
#[allow(dead_code)]
pub struct EncodeOptions {
    pub secret: Option<String>,
    pub private_key_path: Option<PathBuf>,
    pub algorithm: String,
    pub no_signature: bool,
    pub headers: Vec<(String, String)>,
    pub compress: bool,
    pub jwe: bool,
}

/// Encodes JSON data into a JWT token with various algorithm and signing options
#[allow(clippy::too_many_arguments)]
pub fn execute(
    json_str: &str,
    secret: Option<&str>,
    private_key_path: Option<&PathBuf>,
    algorithm: &str,
    no_signature: bool,
    headers: &[(String, String)],
    compress: bool,
    jwe: bool,
) {
    if jwe {
        utils::log_info(format!(
            "Encoding JSON to JWE with encryption: {}",
            "A256GCM".bright_green()
        ));
        if let Err(e) = encode_jwe(json_str, secret) {
            utils::log_error(format!("JWE Encode Error: {e}"));
            utils::log_error("e.g jwt-hack encode {JSON} --jwe --secret={YOUR_SECRET}");
        }
    } else {
        utils::log_info(format!(
            "Encoding JSON to JWT with algorithm: {}",
            algorithm.bright_green()
        ));

        if let Err(e) = encode_json(
            json_str,
            secret,
            private_key_path,
            algorithm,
            no_signature,
            headers,
            compress,
        ) {
            utils::log_error(format!("JSON Encode Error: {e}"));
            utils::log_error("e.g jwt-hack encode {JSON} --secret={YOUR_SECRET}");
            utils::log_error(
                "or with RSA: jwt-hack encode {JSON} --private-key=private.pem --algorithm=RS256",
            );
        }
    }
}

/// Helper function to convert header vector to optional hashmap
fn create_header_map(headers: &[(String, String)]) -> Option<HashMap<&str, &str>> {
    if headers.is_empty() {
        None
    } else {
        let mut map = HashMap::new();
        for (key, value) in headers {
            map.insert(key.as_str(), value.as_str());
        }
        Some(map)
    }
}

/// Helper function to display encoding success information
fn display_encoding_result(
    token: &str,
    algorithm: &str,
    key_info: &str,
    headers: &[(String, String)],
) {
    utils::log_success("JWT token created successfully");

    // Display algorithm information
    println!("\n{}", "━━━ Encoding Details ━━━".bright_cyan().bold());
    utils::log_info(format!("Algorithm: {}", algorithm.bright_green()));
    utils::log_info(format!("Key: {}", key_info));

    // Display any custom headers included in the token
    if !headers.is_empty() {
        utils::log_info("Custom Headers:".to_string());
        for (key, value) in headers {
            println!("  • {}: {}", key.bright_blue(), value.bright_yellow());
        }
    }

    // Display the generated JWT token with color-coded segments
    println!("\n{}", "━━━ JWT Token ━━━".bright_magenta().bold());
    let formatted_token = utils::format_jwt_token(token);
    println!("{formatted_token}\n");
}

fn encode_json(
    json_str: &str,
    secret: Option<&str>,
    private_key_path: Option<&PathBuf>,
    algorithm: &str,
    no_signature: bool,
    headers: &[(String, String)],
    compress: bool,
) -> Result<()> {
    // Parse the input JSON into a Value object
    let claims: Value = serde_json::from_str(json_str)?;

    let progress = utils::start_progress("Encoding JWT token...");

    // Convert custom header key-value pairs into a hashmap for JWT encoding
    let header_map = create_header_map(headers);

    // Build JWT encoding options based on provided parameters
    // Private key option is handled separately due to Rust lifetime requirements
    let options = if no_signature {
        // Use 'none' algorithm (creates unsigned JWT token)
        jwt::EncodeOptions {
            algorithm: "none",
            key_data: jwt::KeyData::None,
            header_params: header_map,
            compress_payload: compress,
        }
    } else if let Some(path) = private_key_path {
        // Read RSA/EC private key from file for asymmetric algorithms
        let key_content = fs::read_to_string(path)?;

        // Create encoding options with the private key content (keeping ownership in this scope)
        let options = jwt::EncodeOptions {
            algorithm,
            key_data: jwt::KeyData::PrivateKeyPem(&key_content),
            header_params: header_map,
            compress_payload: compress,
        };

        // Encode JWT immediately while private key content is in scope
        let token = jwt::encode_with_options(&claims, &options)?;

        progress.finish_and_clear();

        let key_info = format!(
            "{} ({})",
            path.display().to_string().bright_yellow(),
            "Private Key".bright_cyan()
        );
        display_encoding_result(&token, algorithm, &key_info, headers);

        return Ok(());
    } else {
        // Default case: use HMAC with provided secret (or empty string)
        jwt::EncodeOptions {
            algorithm,
            key_data: jwt::KeyData::Secret(secret.unwrap_or("")),
            header_params: header_map,
            compress_payload: compress,
        }
    };

    // Encode the JWT token using the configured options
    let token = jwt::encode_with_options(&claims, &options)?;

    progress.finish_and_clear();

    // Determine key information display based on signature type
    let key_info = if no_signature || secret.unwrap_or("").is_empty() {
        "None (unsigned)".dimmed().to_string()
    } else {
        "****".bright_yellow().to_string()
    };

    display_encoding_result(&token, algorithm, &key_info, headers);

    Ok(())
}

fn encode_jwe(json_str: &str, secret: Option<&str>) -> Result<()> {
    // Parse JSON string to ensure it's valid
    let _claims: Value = serde_json::from_str(json_str)?;

    // Use default secret if none provided
    let key = secret.unwrap_or("default_jwe_key");

    // Start progress indicator
    let progress = utils::start_progress("Encoding JWE token...");

    // Create JWE token using our demo function
    let token = jwt::encode_jwe_demo(json_str, key)?;

    progress.finish_and_clear();

    // Display successful encoding result
    utils::log_success("JWE token created successfully");

    // Display encoding details
    println!("\n{}", "━━━ JWE Encoding Details ━━━".bright_cyan().bold());
    utils::log_info(format!(
        "Key Management Algorithm: {}",
        "dir".bright_green()
    ));
    utils::log_info(format!(
        "Content Encryption Algorithm: {}",
        "A256GCM".bright_green()
    ));
    utils::log_info("Note: This is a demonstration JWE token for testing purposes");

    // Display the JWE token
    println!("\n{}", "━━━ JWE Token ━━━".bright_yellow().bold());
    println!("{}", token);

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::tempdir;

    #[test]
    fn test_execute_with_secret() {
        // Create a simple JSON payload
        let json_str = r#"{"sub":"1234567890","name":"John Doe"}"#;
        let secret = Some("test_secret");
        let private_key_path = None;
        let algorithm = "HS256";
        let no_signature = false;
        let headers = Vec::new();

        // Execute should not panic
        let result = std::panic::catch_unwind(|| {
            execute(
                json_str,
                secret,
                private_key_path,
                algorithm,
                no_signature,
                &headers,
                false, // compress
                false, // jwe
            );
        });

        assert!(result.is_ok(), "execute() panicked with valid parameters");
    }

    #[test]
    fn test_execute_with_no_signature() {
        // Create a simple JSON payload
        let json_str = r#"{"sub":"1234567890","name":"John Doe"}"#;
        let secret = None;
        let private_key_path = None;
        let algorithm = "none";
        let no_signature = true;
        let headers = Vec::new();

        // Execute should not panic
        let result = std::panic::catch_unwind(|| {
            execute(
                json_str,
                secret,
                private_key_path,
                algorithm,
                no_signature,
                &headers,
                false, // compress
                false, // jwe
            );
        });

        assert!(result.is_ok(), "execute() panicked with no signature");
    }

    #[test]
    fn test_execute_with_custom_headers() {
        // Create a simple JSON payload
        let json_str = r#"{"sub":"1234567890","name":"John Doe"}"#;
        let secret = Some("test_secret");
        let private_key_path = None;
        let algorithm = "HS256";
        let no_signature = false;
        let headers = vec![
            ("kid".to_string(), "1234".to_string()),
            ("typ".to_string(), "JWT+AT".to_string()),
        ];

        // Execute should not panic
        let result = std::panic::catch_unwind(|| {
            execute(
                json_str,
                secret,
                private_key_path,
                algorithm,
                no_signature,
                &headers,
                false, // compress
                false, // jwe
            );
        });

        assert!(result.is_ok(), "execute() panicked with custom headers");
    }

    #[test]
    fn test_execute_with_invalid_json() {
        // Create an invalid JSON payload
        let json_str = r#"{"sub":"1234567890","name":"John Doe"#; // Missing closing brace
        let secret = Some("test_secret");
        let private_key_path = None;
        let algorithm = "HS256";
        let no_signature = false;
        let headers = Vec::new();

        // Execute should handle the error and not panic
        let result = std::panic::catch_unwind(|| {
            execute(
                json_str,
                secret,
                private_key_path,
                algorithm,
                no_signature,
                &headers,
                false, // compress
                false, // jwe
            );
        });

        assert!(result.is_ok(), "execute() panicked with invalid JSON");
    }

    #[test]
    fn test_encode_json_with_rsa_key() {
        // This test requires creating a temporary RSA key file
        let temp_dir = tempdir().expect("Failed to create temp directory");
        let key_path = temp_dir.path().join("test_key.pem");

        // Write sample RSA private key (this is just a placeholder for testing)
        let sample_key = "-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWw\nkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mr\nm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEi\nNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV\n3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2\nQU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB\n-----END RSA PRIVATE KEY-----";
        std::fs::write(&key_path, sample_key).expect("Failed to write test key file");

        // Create a simple JSON payload
        let json_str = r#"{"sub":"1234567890","name":"John Doe"}"#;
        let secret = None;
        let private_key_path = Some(&key_path);
        let algorithm = "RS256";
        let no_signature = false;
        let headers = Vec::new();

        // Execute with RSA key shouldn't panic (even if the key is invalid for actual signing)
        let result = std::panic::catch_unwind(|| {
            encode_json(
                json_str,
                secret,
                private_key_path,
                algorithm,
                no_signature,
                &headers,
                false, // compress
            )
        });

        assert!(
            result.is_err() || result.is_ok(),
            "Properly handled RSA key attempt"
        );

        // Clean up
        temp_dir.close().expect("Failed to clean up temp directory");
    }

    #[test]
    fn test_execute_with_jwe_flag() {
        // Test JWE encoding execution
        let json_str = r#"{"sub":"test","name":"JWE User"}"#;
        let secret = Some("test_secret");
        let private_key_path = None;
        let algorithm = "HS256";
        let no_signature = false;
        let headers = Vec::new();
        let compress = false;
        let jwe = true;

        // Execute with JWE flag shouldn't panic
        let result = std::panic::catch_unwind(|| {
            execute(
                json_str,
                secret,
                private_key_path,
                algorithm,
                no_signature,
                &headers,
                compress,
                jwe,
            );
        });

        assert!(result.is_ok(), "execute() with JWE flag should not panic");
    }

    #[test]
    fn test_encode_jwe_function() {
        // Test the encode_jwe function directly
        let json_str = r#"{"sub":"test","name":"JWE User"}"#;
        let secret = Some("test_secret");

        let result = encode_jwe(json_str, secret);
        assert!(result.is_ok(), "encode_jwe should succeed with valid JSON");
    }
}
